import React, { PureComponent } from 'react';

/**
 * 获取相对东八区的时间偏移，用来矫正时间
 *
 * @exports WPT/Util/diffTime
 */
export function diffTime() {
    const d = new Date();
    return (8 * 60 + d.getTimezoneOffset()) * 60;
}

/**
 * 时间戳变字符串
 *
 * @exports WPT/Util/formatDate
 * @param startDate
 * @param updateDate
 * @return
 */
export function formatDate(format, timestamp) {
    // y-m-d H:i:s
    /**
     * example 1: date('y-m-d H:i:s', 1062402400);
     * returns 1: '09:09:40 m is month'
     * example 2: date('F j, Y, g:i a', 1062462400);
     * returns 2: 'September 2, 2003, 2:26 am'
     * example 3: date('Y W o', 1062462400);
     * returns 3: '2003 36 2003'
     * example 4: x = date('Y m d', (new Date()).getTime()/1000);
     * example 4: (x+'').length == 10 // 2009 01 09
     * returns 4: true
     * example 5: date('W', 1104534000);
     * returns 5: '53'
     * example 6: date('B t', 1104534000);
     * returns 6: '999 31'
     * example 7: date('W U', 1293750000.82); // 2010-12-31
     * returns 7: '52 1293750000'
     * example 8: date('W', 1293836400); // 2011-01-01
     * returns 8: '52'
     * example 9: date('W Y-m-d', 1293974054); // 2011-01-02
     * returns 9: '52 2011-01-02'
     * */
    let jsdate;
    let f;
    const txtWords = [
        'Sun',
        'Mon',
        'Tues',
        'Wednes',
        'Thurs',
        'Fri',
        'Satur',
        'January',
        'February',
        'March',
        'April',
        'May',
        'June',
        'July',
        'August',
        'September',
        'October',
        'November',
        'December',
    ];
    const formatChr = /\\?(.?)/gi;
    const formatChrCb = function (t, s) {
        return f[t] ? f[t]() : s;
    };
    const _pad = function (n, c) {
        n = String(n);
        while (n.length < c) {
            n = `0${n}`;
        }
        return n;
    };
    f = {
        // Day
        d() {
            // Day of month w/leading 0; 01..31
            return _pad(f.j(), 2);
        },
        D() {
            // Shorthand day name; Mon...Sun
            return f.l().slice(0, 3);
        },
        j() {
            // Day of month; 1..31
            return jsdate.getDate();
        },
        l() {
            // Full day name; Monday...Sunday
            return `${txtWords[f.w()]}day`;
        },
        N() {
            // ISO-8601 day of week; 1[Mon]..7[Sun]
            return f.w() || 7;
        },
        S() {
            // Ordinal suffix for day of month; st, nd, rd, th
            const j = f.j();
            let i = j % 10;
            if (i <= 3 && parseInt((j % 100) / 10, 10) == 1) {
                i = 0;
            }
            return ['st', 'nd', 'rd'][i - 1] || 'th';
        },
        w() {
            // Day of week; 0[Sun]..6[Sat]
            return jsdate.getDay();
        },
        z() {
            // Day of year; 0..365
            const a = new Date(f.Y(), f.n() - 1, f.j());
            const b = new Date(f.Y(), 0, 1);
            return Math.round((a - b) / 864e5);
        },

        // Week
        W() {
            // ISO-8601 week number
            const a = new Date(f.Y(), f.n() - 1, f.j() - f.N() + 3);
            const b = new Date(a.getFullYear(), 0, 4);
            return _pad(1 + Math.round((a - b) / 864e5 / 7), 2);
        },

        // Month
        F() {
            // Full month name; January...December
            return txtWords[6 + f.n()];
        },
        m() {
            // Month w/leading 0; 01...12
            return _pad(f.n(), 2);
        },
        M() {
            // Shorthand month name; Jan...Dec
            return f.F().slice(0, 3);
        },
        n() {
            // Month; 1...12
            return jsdate.getMonth() + 1;
        },
        t() {
            // Days in month; 28...31
            return new Date(f.Y(), f.n(), 0).getDate();
        },

        // Year
        L() {
            // Is leap year?; 0 or 1
            const j = f.Y();
            return ((j % 4 === 0) & (j % 100 !== 0)) | (j % 400 === 0); /* eslint-disable-line no-bitwise */
        },
        o() {
            // ISO-8601 year
            const n = f.n();
            const W = f.W();
            const Y = f.Y();
            return Y + (n === 12 && W < 9 ? 1 : n === 1 && W > 9 ? -1 : 0);
        },
        Y() {
            // Full year; e.g. 1980...2010
            return jsdate.getFullYear();
        },
        y() {
            // Last two digits of year; 00...99
            return f.Y().toString().slice(-2);
        },
        // Time
        a() {
            // am or pm
            return jsdate.getHours() > 11 ? 'pm' : 'am';
        },
        A() {
            // AM or PM
            return f.a().toUpperCase();
        },
        B() {
            // Swatch Internet time; 000..999
            const H = jsdate.getUTCHours() * 36e2;
            // Hours
            const i = jsdate.getUTCMinutes() * 60;
            // Minutes
            // Seconds
            const s = jsdate.getUTCSeconds();
            return _pad(Math.floor((H + i + s + 36e2) / 86.4) % 1e3, 3);
        },
        g() {
            // 12-Hours; 1..12
            return f.G() % 12 || 12;
        },
        G() {
            // 24-Hours; 0..23
            return jsdate.getHours();
        },
        h() {
            // 12-Hours w/leading 0; 01..12
            return _pad(f.g(), 2);
        },
        H() {
            // 24-Hours w/leading 0; 00..23
            return _pad(f.G(), 2);
        },
        i() {
            // Minutes w/leading 0; 00..59
            return _pad(jsdate.getMinutes(), 2);
        },
        s() {
            // Seconds w/leading 0; 00..59
            return _pad(jsdate.getSeconds(), 2);
        },
        u() {
            // Microseconds; 000000-999000
            return _pad(jsdate.getMilliseconds() * 1000, 6);
        },

        // Timezone
        e() {
            throw new Error('Not supported (see source code of date() for timezone on how to add support)');
        },
        I() {
            // DST observed?; 0 or 1
            // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC.
            // If they are not equal, then DST is observed.
            const a = new Date(f.Y(), 0);
            // Jan 1
            const c = Date.UTC(f.Y(), 0);
            // Jan 1 UTC
            const b = new Date(f.Y(), 6);
            // Jul 1
            // Jul 1 UTC
            const d = Date.UTC(f.Y(), 6);
            return a - c !== b - d ? 1 : 0;
        },
        O() {
            // Difference to GMT in hour format; e.g. +0200
            const tzo = jsdate.getTimezoneOffset();
            const a = Math.abs(tzo);
            return (tzo > 0 ? '-' : '+') + _pad(Math.floor(a / 60) * 100 + (a % 60), 4);
        },
        P() {
            // Difference to GMT w/colon; e.g. +02:00
            const O = f.O();
            return `${O.substr(0, 3)}:${O.substr(3, 2)}`;
        },
        T() {
            return 'UTC';
        },
        Z() {
            // Timezone offset in seconds (-43200...50400)
            return -jsdate.getTimezoneOffset() * 60;
        },

        // Full Date/Time
        c() {
            // ISO-8601 date.
            return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb);
        },
        r() {
            // RFC 2822
            return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb);
        },
        U() {
            // Seconds since UNIX epoch
            return (jsdate / 1000) | 0; /* eslint-disable-line no-bitwise */
        },
    };
    const date = (temp, times) => {
        let time = 0;
        if (times === undefined) {
            time = Date.now();
        } else if (times instanceof Date) {
            time = times;
        } else {
            time = times * 1000;
        }
        time += 1000 * diffTime();
        jsdate = new Date(time);
        return temp.replace(formatChr, formatChrCb);
    };
    return date(format, timestamp);
}

/**
 * 获取时间戳
 *
 * @exports WPT/Util/getStamp
 * @param {string} timeStr Y-m-d h:i:s
 * @return {num}
 */
export function getStamp(timeStr) {
    if (!timeStr) return 0;

    if (!Number.isNaN(Number(timeStr))) {
        return Number(timeStr);
    }

    let [year, month, date, hrs, min, sec] = [0, 0, 0, 0, 0, 0]; // 年月日, 时,分,秒
    timeStr = timeStr.replace(/-/g, '/').trim();
    const timeArr = timeStr.split(/\s+/); // timeArr[0]为年月日 timeArr[1]为时分秒
    if (/\//g.test(timeArr[0])) {
        [date, month, year = new Date().getFullYear()] = timeArr[0]
            .split('/')
            .reverse()
            .map((PER) => Number(PER));
    }
    if (/:/g.test(timeArr[1])) {
        [hrs, min, sec = 0] = timeArr[1].split(':');
    }
    const timestamp = new Date(year, month - 1, date, hrs, min, sec).getTime() / 1000;
    if (timestamp < 0) {
        console.error('请输入正确的格式: m-d h:i');
    }
    return timestamp;
}

/**
 * 判断(当前时间)是在某个时间断之前
 *  currentTime 是否在 targetTime 前面
 *
 * @exports WPT/Util/isBefore
 * @param targetTime
 * @param currentTime
 * @return
 */
export function isBefore(targetTime, currentTime = WPT.nowTime) {
    [targetTime, currentTime] = [targetTime, currentTime].map((time) => getStamp(time)); // 是格式化时间就转一下
    return currentTime <= targetTime;
}
/**
 * 判断(当前时间)是在某个时间断之后
 * currentTime 是否在targetTime后面
 *
 * @exports WPT/Util/isAfter
 * @param targetTime
 * @param currentTime
 * @return
 */
export function isAfter(targetTime, currentTime = WPT.nowTime) {
    [targetTime, currentTime] = [targetTime, currentTime].map((time) => getStamp(time)); // 是格式化时间就转一下
    return currentTime >= targetTime;
}

/**
 * 判断是在某个时间断之内
 *
 * @exports WPT/Util/isIn
 * @param startTime
 * @param endTime
 * @param currentTime
 * @return
 */
export function isIn(startTime, endTime, currentTime = WPT.nowTime) {
    // 在结束时间之前 并且在开始时间之后
    return isBefore(endTime, currentTime) && isAfter(startTime, currentTime);
}

/**
 * 把秒变成时间格式
 *
 * @exports WPT/Util/formatCountDownTime
 * @param fmt
 * @param time
 * @return
 */
export function formatCountDownTime(time, fmt = 'd天hh时ii分ss秒') {
    // 查看日期格式里是否包含年月日时分秒
    const [y, M, d, h, i, s] = ['y', 'M', 'd', 'h', 'i', 's'].map((PER) => {
        const result = fmt.indexOf(PER) >= 0 ? 1 : 0;
        return result;
    });

    const [year, month, date, hrs, min, sec] = [
        y * 86400 * 30 * 12,
        M * 86400 * 30,
        d * 86400,
        h * 3600,
        i * 60,
        s,
    ].map((PER) => {
        const result = PER == 0 ? 0 : Math.floor(time / PER);
        if (PER != 0) {
            time %= PER;
        }
        return result;
    });
    const INFO = {
        y: year,
        M: month,
        d: date,
        h: hrs,
        i: min,
        s: sec,
    };
    const fillZero = (v, len = 2) => {
        if (len == 2) {
            return `${'0'.repeat(Math.max(0, len - `${v}`.length))}${v}`;
        }
        return v;
    };
    return fmt.replace(/(y){1,2}|M{1,2}|(d){1,2}|h{1,2}|i{1,2}|s{1,2}/g, (str) => fillZero(INFO[str[0]], str.length));
}

// 秒级倒计时
export class Countdown extends PureComponent {
    static defaultProps = {
        nowTime: WPT.nowTime,
        endTime: WPT.nowTime,
        isIncludeZero: true, // 倒计时是否要包含0那个时刻
        format: 'hh时ii分ss秒', // 时间格式
        onChange: () => {},
        onEnd: () => {},
        customRender: null, // 自定义 render
        numClassName: '', // 倒计时数字的样式
    };

    state = {
        leftTime: 0,
    };

    intervalKey = WPT.Util.uuid();

    baseTime = Math.floor(Date.now() / 1000); // 时间差

    componentDidMount() {
        this.autoRun();
    }

    UNSAFE_componentWillReceiveProps(nextProps) {
        if (nextProps.nowTime != this.props.nowTime) {
            this.baseTime = Math.floor(Date.now() / 1000); // 时间差nowTime变了初始化下
        }
    }

    componentWillUnmount() {
        this.clearInterval();
    }

    clearInterval = () => {
        WPT.Interval.clear(this.intervalKey);
    };

    autoRun = () => {
        this.clearInterval();

        WPT.Interval.append(this.intervalKey, () => {
            const { nowTime, endTime, isIncludeZero } = this.props;
            const leftTime = Math.floor(Date.now() / 1000) - this.baseTime; // 已经过去的时间

            this.props.onChange(nowTime + leftTime); // 时间发生变化

            if (isIncludeZero) {
                if (leftTime > endTime - nowTime) {
                    this.clearInterval();
                    this.props.onEnd(); // 倒计时结束
                    return;
                }
            } else if (endTime - nowTime - parseInt(leftTime) < 1) {
                this.clearInterval();
                this.props.onEnd(); // 倒计时结束
                return;
            }

            this.setState({
                leftTime,
            });
        });
    };

    render() {
        const { className = '', numClassName = '' } = this.props;
        const cutDwonTime = this.props.endTime - this.props.nowTime - this.state.leftTime; // 剩余多少秒
        if (cutDwonTime < 0) {
            return null;
        }

        const html = formatCountDownTime(cutDwonTime, this.props.format).replace(
            /\d+/g,
            ($0) => `<span class=${numClassName}>${$0}</span>`
        );

        if (typeof this.props.customRender === 'function') {
            return this.props.customRender(cutDwonTime, html);
        }

        return <div dangerouslySetInnerHTML={{ __html: html }} className={className} />;
    }
}

/**
 * 倒计时
 *
 * @exports WPT/Util/getCountdown
 * @param {Object} [options={}]
 */
export function getCountdown(
    options = {
        isIncludeZero: true,
        endTime: WPT.nowTime,
        nowTime: WPT.nowTime,
        className: '',
        numClassName: '',
        fmt: 'hh时ii分ss秒',
        onChange: () => {},
        onEnd: () => {},
    }
) {
    return (
        <Countdown
            key={options.key ? options.key : options.endTime}
            className={options.className}
            numClassName={options.numClassName}
            isIncludeZero={options.isIncludeZero}
            nowTime={options.nowTime}
            endTime={options.endTime}
            format={options.fmt}
            onChange={options.onChange}
            onEnd={options.onEnd}
            customRender={options.customRender}
        />
    );
}
